//: src/com/silvtar/sync/model/SSTree.java
/* 
 * Created by 朴晟宁 <piaosn@gmail.com> on 4/21/2013. 
 * Copyright (c) 2013 朴晟宁. All rights reserved.
 * 
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program;
 * 
 * *********************************************************************
 * 
 * Modify History:
 * Date        Version    Description
 * 4/20/2013   0.1        Create the class
 * 4/23/2013   0.2        It can build filetree with MD5 now.
 * 5/11/2013   0.3        Serializable
 */

package com.silvtar.sync.model;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Date;

import com.silvtar.io.StdOut;
import com.silvtar.util.Md5;

/**
 * 文件树类 本类为一个文件树。当中包含了目录结构以及文件属性。
 * 
 * @author 朴晟宁/Silvtar
 * @author piaosn@gmail.com
 * @version 0.3
 */
public class SSTree implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    private SSTreeNode        root             = null; // 根节点，根目录的文件节点

    private String            path             = null; // 根目录，根节点的路径

    private boolean           useMd5           = true; // 生成文件树时是否计算MD5

    // ---------------------------- getter && setter ---------------------------
    /**
     * 获得根节点
     * 
     * @return 根节点
     */
    public SSTreeNode getRoot() {

        return root;
    }

    /**
     * 查看文件树中是否启用了MD5校验
     * 
     * @return 文件树中是否启用了MD5校验
     */
    public boolean isUseMd5() {

        return useMd5;
    }

    /**
     * 设置文件树是否要启用MD5校验，如果启用，则在以后的处理过程中都会使用MD5校验
     * 
     * @param useMd5
     *            是否使用MD5校验
     */
    public void setUseMd5(boolean useMd5) {

        this.useMd5 = useMd5;
    }

    /**
     * 获得根目录
     * 
     * @return 根目录
     */
    public String getPath() {

        return path;
    }

    // --------------------------------- 构造方法 --------------------------------
    /**
     * 如使用这个构造方法，还要再调用buildFileTree()方法。
     */
    public SSTree() {

        root = new SSTreeNode();
        root.setName("");
        root.setRelativePath("");
        root.setDir(true);
    }

    /**
     * 根据根目录构造文件树
     * 
     * @param path
     *            根目录
     * @throws SSException
     *             当根目录不存在时会抛出这个异常
     */
    public SSTree(String path){

        root = new SSTreeNode();
        root.setName("");
        root.setRelativePath("");
        root.setDir(true);

        this.path = path;
        if (scanDirMain() < 0) {
            StdOut.printErr("目录" + path + "不是目录");
        }
    }

    /**
     * 设置根目录并开始创建文件树。如想对构建进行设置，请在这个方法之前进行。
     * 
     * @param path
     *            根目录
     * @throws SSException
     *             当根目录不存在时会抛出这个异常
     */
    public void builtFileTree(String path) throws SSException {

        this.path = path;
        if (scanDirMain() < 0) {
            throw new SSException(path + " isn't exist");
        }
    }

    // --------------------------------- 主要方法 --------------------------------

    /*
     * 按顺序打印出该目录树中包含的所有文件。
     */
    public void printFileTree() {

        root.print();
    }

    /**
     * 输出为XML，本方法用于将本类实例化为xml文件，用作传输。
     */
    public void printXML() {
        root.printXML();
    }

    /**
     * 扫描方法（1） 本方法用于扫描一个文件夹，并将它的目录结构和文件属性存入文件数中 下面两个方法为一组方法，只有第一个方法应该被外部调用。
     * 
     * @return 正常结束时返回0，文件夹不存在时返回-1
     */
    private int scanDirMain() {

        File dir = new File(path);
        if (!dir.exists()) {
            return -1;
        }

        scanDir(root);

        return 0;
    }

    /**
     * 扫描方法（2） 本方法用于扫描一个文件夹，并将它的目录结构和文件属性存入文件树中
     * 
     * @return 0为正常结束 -2表示读取到了无法打开的文件夹
     */
    private int scanDir(SSTreeNode currentNode) {

        File dir = new File(path + currentNode.getRelativePath());
        String[] list;

        // 判断文件夹是否是一个无法扫描的文件夹。（比如osx下以文件形式存在的文件夹）。如果是，则把它的属性设置为文件，并写入错误日志。
        if (null == (list = dir.list())) {
            currentNode.setDir(false);
            StdOut.printWarningLog(this,
                    "\"" + path + currentNode.getRelativePath()
                            + "\" 看起来是一个文件夹，但是程序无法读取它的文件列表。将把它作为文件处理。");
            return -2;
        }

        list = dir.list();

        for (int i = 0; i < list.length; i++) {
            SSTreeNode currentChild = new SSTreeNode(); // 当前正在处理的文件节点
            if ("." == list[i] || ".." == list[i])
                continue;

            // 获得文件名
            currentChild.setName(list[i]);

            // 获得文件相对路径
            currentChild.setRelativePath(currentNode.getRelativePath() + '/'
                    + currentChild.getName());

            // 获得文件大小和修改时间
            File file = new File(dir, list[i]);
            currentChild.setDate(new Date(file.lastModified()));
            currentChild.setSize(file.length());

            // 判断是否为链接文件（link file），是否为文件夹。链接文件被当作文件处理。
            try {
                if (file.getCanonicalPath().equals(file.getAbsolutePath())
                        && file.isDirectory()) {
                    currentChild.setDir(true);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 如果是文件，计算MD5
            if (!(true == currentChild.isDir())) {
                if (useMd5) {
                    String tMd5 = null;
                    try {
                        tMd5 = Md5.getFileMD5String(file);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    currentChild.setMd5(tMd5);
                }
            }

            // 如果是目录，则进入递归
            else {
                if (!(currentChild.getName() == "." || currentChild.getName() == "..")) {
                    // Out.println("scaning " + path +
                    // currentChild.getRelativePath());
                    scanDir(currentChild);
                }
            }

            // 把处理好的节点加入到vector中
            currentNode.getChild().add(currentChild);
        }
        return 0;
    }
}
